<!DOCTYPE html>
<html lang="zh-hans">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <!-- <script src="./src/vue.global.js"></script> -->
    <script src="https://cdn.bootcdn.net/ajax/libs/vue/3.1.5/vue.global.js"></script>
    <style>
        input[type=checkbox] {
            margin: 0 5px;
            cursor: pointer;
            font-size: 14px;
            width: 15px;
            /* height: 12px; */
            height: 15px;
            position: relative;
        }

        input[type=checkbox]:after {
            position: absolute;
            /* width: 10px; */
            width: 15px;
            height: 15px;
            top: 0;
            content: " ";
            background-color: #fff;
            color: rgb(255, 255, 255);
            border: 1px #888 solid;
            display: inline-block;
            visibility: visible;
            /* padding: 0px 3px; */
            border-radius: 3px;
        }

        input[type=checkbox]:checked:after {
            content: "✓";
            font-size: 12px;
            background-color: #ff0000;
            border: 0;
            display: flex;
            justify-content: center;
            align-items: center;
        }

        /* half */
        .half[type=checkbox] {
            margin: 0 5px;
        }

        .half[type=checkbox]:after {
            position: absolute;
            width: 10px;
            height: 15px;
            top: 0;
            content: " ";
            background-color: #fff;
            color: #fff;
            display: inline-block;
            visibility: visible;
            padding: 0px 3px;
            border-radius: 3px;
            border: 2px solid red;
        }

        .half[type=checkbox]:checked:after {
            content: " ";
            font-size: 12px;
            border: 0;
        }
    </style>
</head>
<body>
    <div id="app">
        <button @click="unwindAll">{{ unwind ? '收起' : '展开'}}</button>
        <button @click="changeStruct">切换</button>
        <selects ref="select" :list="list"></selects>
    </div>
    <script>
        const app = Vue.createApp({
            data() {
                return {
                    origin: [],
                    list: [{
                            title: "水果",
                            children: [{
                                    title: "香蕉",
                                    // children: [{
                                    //         title: "香蕉A",
                                    //         children: [{
                                    //                 title: "香蕉AA"
                                    //             },
                                    //             {
                                    //                 title: "香蕉BB"
                                    //             }
                                    //         ]
                                    //     },
                                    //     {
                                    //         title: "香蕉B"
                                    //     }
                                    // ]
                                },
                                // {
                                //     title: "苹果"
                                // }, {
                                //     title: "梨"
                                // }, {
                                //     title: "草莓"
                                // }
                            ]
                        },
                        // {
                        //     title: "蔬菜",
                        //     children: [{
                        //         title: "芹菜"
                        //     }, {
                        //         title: "菠菜"
                        //     }, {
                        //         title: "土豆"
                        //     }, {
                        //         title: "白菜"
                        //     }]
                        // }
                    ],
                    unwind: false,
                    flag: "two",
                }
            },
            methods: {
                changeStruct() {
                    let origin = JSON.parse(JSON.stringify(this.origin))
                    this.generate(origin, 0, this.flag)
                    this.transform(origin)
                    this.list = origin
                    this.flag = this.flag === "two" ? "one" : "two"
                    setTimeout(() => {
                        this.windAll(this.unwind)
                    })
                },
                windAll(unwind) {
                    let tree = this.$refs.select.$.subTree.children
                    let stack = [...tree]
                    while (stack.length) {
                        let top = stack.pop()
                        if (top.component) {
                            top.component.data.isShow = unwind
                            // console.log(top.component)
                        }
                        if (top.children && Array.isArray(top.children)) {
                            stack.push(...top.children)
                        }
                        if (top.component && top.component.subTree.children) {
                            stack.push(...top.component.subTree.children)
                        }
                    }
                },
                unwindAll() {
                    this.unwind = !this.unwind
                    // console.log(this.$refs.select.$.subTree.children)
                    this.windAll(this.unwind)
                },
                generate(source, gcount, type) {
                    type = type ? type : "one"
                    // console.log(type)
                    if (gcount >= 5) return
                    let stack = [...source]
                    while (stack.length) {
                        let top = stack.pop()
                        // console.log(top)
                        if (top.children) {
                            stack.push(...top.children)
                        } else {
                            top.children = []
                            let temp = {}
                            if (type === 'two') {
                                temp = [{
                                    title: `${top.title}A`
                                }, {
                                    title: `${top.title}B`
                                }]
                            } else if (type === 'one') {
                                temp = [{
                                    title: `${top.title}A`
                                }]
                            }
                            top.children.push(...temp)
                            this.generate(top.children, gcount + 1, type)
                        }
                    }
                },
                transform(source) {
                    console.log("transform")
                    let stack = [...source]
                    let childCount = 0
                    let parent = undefined
                    while (stack.length) {
                        let top = stack.pop()
                        // console.log(top)
                        if (top.children) {
                            if (childCount) {
                                stack.push(top)
                                let length = stack.length
                                while (childCount) {
                                    stack[length - 1].parent = parent
                                    childCount--
                                    length--
                                }
                                stack.pop()
                            }
                            childCount = top.children.length
                            parent = top
                            stack.push(...top.children)
                        } else {
                            if (childCount) {
                                top.parent = parent
                                childCount--
                            }
                        }
                    }
                    console.log(source)
                }
            },
            mounted() {
                this.origin = JSON.parse(JSON.stringify(this.list))
                this.generate(this.list, 0, "one")
                this.transform(this.list)
            },
        })

        app.component("selects", {
            data() {
                return {}
            },
            methods: {
                oks(child) {
                    console.log("selects")
                    console.log(child)
                    let checked = !child.checked
                    let stack = [child]
                    while (stack.length) {
                        let top = stack.pop()
                        top.checked = checked
                        top.status = undefined
                        if (top.children) {
                            top.status = undefined
                            stack.push(...top.children)
                        }
                    }
                }
            },
            props: ["list"],
            template: `<boxs :list="list" :child="item" v-for="item in list" :key="item.title" @my-parent="oks"></boxs>`
        })

        app.component('boxs', {
            name: "boxs",
            data() {
                return {
                    isShow: false
                }
            },
            props: ["child", 'list'],
            methods: {
                check(child) {
                    // console.log(this)
                    console.log("check")
                    this.$emit('my-parent', child)
                },

                getAllchildren(child) {
                    if (!child.children) return []
                    let all = []
                    let stack = [...child.children]
                    while (stack.length) {
                        let top = stack.pop()
                        all.push(top)
                        if (top.children) {
                            stack.push(...top.children)
                        }
                    }
                    return all
                },

                judgeParent(child) {
                    // parent
                    let parent = child // 选中节点也要判断父亲情况
                    while (parent) {
                        if (parent.children) {
                            let all = this.getAllchildren(parent) // 获取所有孩子
                            let checkes = all.filter(item => item.checked) // 判断孩子的勾选状态
                            if (checkes.length === 0) { // 如果孩子的勾选成功个数为0，则置空父亲状态
                                parent.checked = false
                                parent.status = undefined
                            } else { // 如果孩子的勾选成功个数不为0
                                if (checkes.length === all.length) { // 如果孩子的勾选成功个数为所有孩子个数，父亲勾选状态置为成功，且置父亲状态位空
                                    parent.checked = true
                                    parent.status = undefined
                                } else { // 如果孩子的勾选成功个数不为所有孩子个数，父亲勾选状态置为失败，且置父亲状态为半成功
                                    parent.checked = false
                                    parent.status = "half"
                                }
                            }
                            if (parent.checked) {
                                console.assert(parent.checked === true && parent.status === undefined,
                                    `parent.checked === true && parent.status === undefined is wrong ${parent.title}`
                                )
                            } else {
                                console.assert(parent.checked === false && (parent.status === 'half' || parent
                                        .status === undefined),
                                    `parent.checked === false && parent.status === 'half' is wrong ${parent.title}`
                                )
                            }
                        }
                        parent = parent.parent // 更新父亲
                    }
                },
                oks(child) {
                    console.log("boxs")
                    console.log(child)
                    // 如果有孩子，当前节点取反，将孩子取反，状态置为undefined，然后判断所有父亲节点
                    // 如果没有孩子，当前节点取反，直接判断所有父亲节点

                    // 判断选中节点有没有child
                    if (child.children) { // 如果有孩子
                        let checked = !child.checked // 本身勾选取反
                        // 下面是遍历所有孩子
                        let stack = [child]
                        while (stack.length) {
                            let top = stack.pop()
                            top.checked = checked // 孩子勾选取反
                            top.status = undefined
                            if (top.children) {
                                stack.push(...top.children)
                            }
                        }
                        // 判断父亲
                        this.judgeParent(child)
                    } else { // 如果没有孩子
                        child.checked = !child.checked // 本身勾选取反
                        this.judgeParent(child) // 判断父亲
                    }
                }
            },
            template: `
            <div>
                <i style="cursor: pointer;user-select: none;" v-if="child.children" @click="isShow=!isShow">*</i><i v-else style="visibility: hidden;">*</i>
                <span>
                    <!-- <i v-if="child.status === 'half'">half</i> -->
                    <input :class="{'half': child.status === 'half'}" @click="check(child)" :checked="child.checked" type="checkbox" :id="child.title">
                    <label :for="child.title">{{child.title}}</label>
                </span>
                <div v-show="isShow" style="padding-left: 20px;" v-if="child.children">
                    <boxs :list="list" v-for="item in child.children" :child="item" @my-parent="oks"></boxs>
                </div>
            </div>
            `
        })

        app.mount('#app')
    </script>
</body>
</html>